So the first thing I would check is whether you still have the original coin script installed somewhere in the Mod/API scripts tab. So my read is: the doubling is probably two active coin scripts, while Nigel’s CP problem is probably a genuinely bad cp attribute object on that character. Those are likely two separate problems. This version adds a simple anti-double-fire guard, cleaner diagnostics, and a direct repair command that can reset Nigel’s cp attribute without using ChatSetAttr. Try this: // Coin Change Script - Full Version // Requires ChatSetAttr for !modattr. // Main command expected: // // !changecoins --NUMBER_TO_ADD_OR_SUBTRACT --CHAR_ID --COIN_TYPE --COIN_NAME --CHARACTER_NAME --CURRENT_COINS // // Examples: // !changecoins --10 --CHARACTER_ID --cp --Copper Pieces --Nigel Krenov --80 // !changecoins ---5 --CHARACTER_ID --gp --Gold Pieces --Nigel Krenov --20 // // Debug: // !debugcoinattr --charid CHARACTER_ID --coin cp // // Repair/sync existing coin attributes: // !repaircoinattr --charid CHARACTER_ID --coin cp --value 80 // // Recreate one coin attribute: // !recreatecoinattr --charid CHARACTER_ID --coin cp --value 80 var CoinChanger1E = CoinChanger1E || (function() { "use strict"; const SCRIPT_NAME = "CoinChanger1E"; const VERSION = "2026-07-07"; const RELAUNCH_DELAY_MS = 800; const DUPLICATE_GUARD_MS = 1500; let recentCommands = {}; on("chat:message", function(msg) { if (msg.type !== "api") return; let rawContent = (msg.content || "").trim(); let lowerContent = rawContent.toLowerCase(); if (lowerContent.indexOf("!changecoins") === 0) { if (isDuplicateCommand(msg)) { sendChat(SCRIPT_NAME, whisperGM("Duplicate !changecoins command ignored by guard.")); return; } handleChangeCoins(msg); return; } if (lowerContent.indexOf("!debugcoinattr") === 0) { handleDebugCoinAttr(msg); return; } if (lowerContent.indexOf("!repaircoinattr") === 0) { handleRepairCoinAttr(msg); return; } if (lowerContent.indexOf("!recreatecoinattr") === 0) { handleRecreateCoinAttr(msg); return; } if (lowerContent.indexOf("!coinchangerversion") === 0) { sendChat(SCRIPT_NAME, whisperGM("Running " + SCRIPT_NAME + " version " + VERSION)); return; } }); function handleChangeCoins(msg) { let args = msg.content.trim().split(/\s*--/); let newCoinsRaw = args[1]; let coinCharId = (args[2] || "").trim(); let coinType = (args[3] || "").trim().toLowerCase(); let coinName = (args[4] || "").trim(); let coinCharName = (args[5] || "").trim(); let currentCoinsRaw = args[6]; let newCoins = parseInt(newCoinsRaw, 10); let currentCoins = parseInt(currentCoinsRaw, 10); if (!coinCharId) { sendChat("Coin Error", errorBox("No character ID was supplied.")); return; } if (!coinType) { sendChat("Coin Error", errorBox("No coin type was supplied.")); return; } if (!isValidCoinType(coinType)) { sendChat("Coin Error", errorBox("Unknown coin type: " + escapeHtml(coinType))); return; } if (isNaN(newCoins)) { sendChat("Sorry, but...", errorBox("The coin change must be a number.")); return; } if (isNaN(currentCoins)) { sendChat("Sorry, but...", errorBox("The current number of " + escapeHtml(coinType.toUpperCase()) + " could not be read correctly.")); return; } if (currentCoins + newCoins < 0) { sendChat( "Sorry, but...", errorBox( escapeHtml(String(newCoins)) + " would reduce " + escapeHtml(coinCharName) + "'s " + escapeHtml(coinType.toUpperCase()) + " below 0." ) ); return; } let character = getObj("character", coinCharId); if (!character) { sendChat("Coin Error", errorBox("Could not find character with ID: " + escapeHtml(coinCharId))); return; } let attrs = findObjs({ type: "attribute", characterid: coinCharId, name: coinType }); let sheetValue = getAttrByName(coinCharId, coinType, "current"); sendChat( "Coin Debug", infoBox( "<b>Running:</b> " + SCRIPT_NAME + " " + VERSION + "<br>" + "<b>Character:</b> " + escapeHtml(character.get("name")) + "<br>" + "<b>Coin:</b> " + escapeHtml(coinType.toUpperCase()) + "<br>" + "<b>Sheet value from getAttrByName:</b> [" + escapeHtml(String(sheetValue)) + "]<br>" + "<b>Current coins passed into command:</b> " + escapeHtml(String(currentCoins)) + "<br>" + "<b>Change requested:</b> " + escapeHtml(String(newCoins)) + "<br>" + "<b>Expected result:</b> " + escapeHtml(String(currentCoins + newCoins)) + "<br>" + "<b>Matching attribute objects:</b> " + attrs.length ) ); sendCoinAttributeDetails(character, coinCharId, coinType, attrs); let commandValue = "!modattr --charid " + coinCharId + " --" + coinType + "|" + newCoins; sendChat("Change " + coinType.toUpperCase(), commandValue); setTimeout(function() { let postSheetValue = getAttrByName(coinCharId, coinType, "current"); sendChat( "Coin Debug", infoBox( "<b>After ChatSetAttr delay:</b><br>" + "<b>Character:</b> " + escapeHtml(character.get("name")) + "<br>" + "<b>" + escapeHtml(coinType.toUpperCase()) + " from getAttrByName:</b> [" + escapeHtml(String(postSheetValue)) + "]" ) ); sendChat("My Coins, Altered", "!forselected !mycoins"); }, RELAUNCH_DELAY_MS); } function handleDebugCoinAttr(msg) { let args = parseArgs(msg.content); let charId = args.charid || args.characterid || ""; let coinType = (args.coin || "cp").toLowerCase(); if (!charId) { sendChat("Coin Debug", errorBox("Usage: !debugcoinattr --charid CHARACTER_ID --coin cp")); return; } if (!isValidCoinType(coinType)) { sendChat("Coin Debug", errorBox("Invalid coin type: " + escapeHtml(coinType))); return; } let character = getObj("character", charId); if (!character) { sendChat("Coin Debug", errorBox("No character found for ID: " + escapeHtml(charId))); return; } let attrs = findObjs({ type: "attribute", characterid: charId, name: coinType }); let sheetValue = getAttrByName(charId, coinType, "current"); sendChat( "Coin Debug", infoBox( "<b>Running:</b> " + SCRIPT_NAME + " " + VERSION + "<br>" + "<b>Character:</b> " + escapeHtml(character.get("name")) + "<br>" + "<b>Coin:</b> " + escapeHtml(coinType.toUpperCase()) + "<br>" + "<b>getAttrByName value:</b> [" + escapeHtml(String(sheetValue)) + "]<br>" + "<b>Matching attribute objects:</b> " + attrs.length ) ); sendCoinAttributeDetails(character, charId, coinType, attrs); } function handleRepairCoinAttr(msg) { let args = parseArgs(msg.content); let charId = args.charid || args.characterid || ""; let coinType = (args.coin || "").toLowerCase(); let valueRaw = args.value; let finalValue = parseInt(valueRaw, 10); if (!charId || !coinType || isNaN(finalValue)) { sendChat("Coin Repair", errorBox("Usage: !repaircoinattr --charid CHARACTER_ID --coin cp --value 80")); return; } if (!isValidCoinType(coinType)) { sendChat("Coin Repair", errorBox("Invalid coin type: " + escapeHtml(coinType))); return; } if (finalValue < 0) { sendChat("Coin Repair", errorBox("Coin value cannot be below 0.")); return; } let character = getObj("character", charId); if (!character) { sendChat("Coin Repair", errorBox("No character found for ID: " + escapeHtml(charId))); return; } let attrs = findObjs({ type: "attribute", characterid: charId, name: coinType }); if (!attrs.length) { createObj("attribute", { characterid: charId, name: coinType, current: finalValue }); sendChat( "Coin Repair", infoBox( "Created " + escapeHtml(coinType.toUpperCase()) + " for " + escapeHtml(character.get("name")) + " with value " + escapeHtml(String(finalValue)) + "." ) ); return; } attrs.forEach(function(attr) { attr.set("current", finalValue); }); sendChat( "Coin Repair", infoBox( "Set " + attrs.length + " attribute object(s) named " + escapeHtml(coinType) + " for " + escapeHtml(character.get("name")) + " to " + escapeHtml(String(finalValue)) + ".<br><br>" + "Now reopen the sheet and check whether the visible value matches." ) ); } function handleRecreateCoinAttr(msg) { let args = parseArgs(msg.content); let charId = args.charid || args.characterid || ""; let coinType = (args.coin || "").toLowerCase(); let valueRaw = args.value; let finalValue = parseInt(valueRaw, 10); if (!charId || !coinType || isNaN(finalValue)) { sendChat("Coin Recreate", errorBox("Usage: !recreatecoinattr --charid CHARACTER_ID --coin cp --value 80")); return; } if (!isValidCoinType(coinType)) { sendChat("Coin Recreate", errorBox("Invalid coin type: " + escapeHtml(coinType))); return; } if (finalValue < 0) { sendChat("Coin Recreate", errorBox("Coin value cannot be below 0.")); return; } let character = getObj("character", charId); if (!character) { sendChat("Coin Recreate", errorBox("No character found for ID: " + escapeHtml(charId))); return; } let attrs = findObjs({ type: "attribute", characterid: charId, name: coinType }); let removedCount = attrs.length; attrs.forEach(function(attr) { attr.remove(); }); let newAttr = createObj("attribute", { characterid: charId, name: coinType, current: finalValue }); sendChat( "Coin Recreate", infoBox( "<b>Character:</b> " + escapeHtml(character.get("name")) + "<br>" + "<b>Coin:</b> " + escapeHtml(coinType.toUpperCase()) + "<br>" + "<b>Removed old attributes:</b> " + removedCount + "<br>" + "<b>Created new attribute ID:</b> " + escapeHtml(newAttr.id) + "<br>" + "<b>New value:</b> " + escapeHtml(String(finalValue)) + "<br><br>" + "Close and reopen the character sheet, then test the coin button again." ) ); } function sendCoinAttributeDetails(character, charId, coinType, attrs) { let output = ""; output += "<div style='border:1px solid #999;background:#fff;padding:5px;'>"; output += "<b>Attribute Details</b><br>"; output += "<b>Character:</b> " + escapeHtml(character.get("name")) + "<br>"; output += "<b>Coin:</b> " + escapeHtml(coinType.toUpperCase()) + "<br><br>"; if (!attrs.length) { output += "<span style='color:#c00;'><b>No actual attribute object exists for this coin.</b></span><br>"; output += "The sheet may be showing a default or calculated value.<br>"; } else { attrs.forEach(function(attr, index) { output += "<b>" + escapeHtml(coinType.toUpperCase()) + " attribute " + (index + 1) + "</b><br>"; output += "id: " + escapeHtml(attr.id) + "<br>"; output += "name: " + escapeHtml(attr.get("name")) + "<br>"; output += "current: [" + escapeHtml(String(attr.get("current"))) + "]<br>"; output += "max: [" + escapeHtml(String(attr.get("max"))) + "]<br><br>"; }); } output += "</div>"; sendChat("Coin Debug", output); } function isDuplicateCommand(msg) { let key = [ msg.playerid || "unknown-player", msg.content || "", msg.selected ? JSON.stringify(msg.selected) : "no-selection" ].join("|"); let now = Date.now(); Object.keys(recentCommands).forEach(function(k) { if (now - recentCommands[k] > DUPLICATE_GUARD_MS) { delete recentCommands[k]; } }); if (recentCommands[key] && now - recentCommands[key] <= DUPLICATE_GUARD_MS) { return true; } recentCommands[key] = now; return false; } function isValidCoinType(coinType) { return coinType === "cp" || coinType === "sp" || coinType === "ep" || coinType === "gp" || coinType === "pp"; } function parseArgs(content) { let parts = content.trim().split(/\s+--/); let result = {}; for (let i = 1; i < parts.length; i++) { let part = parts[i]; let firstSpace = part.indexOf(" "); if (firstSpace === -1) { result[part.toLowerCase()] = true; } else { let key = part.substring(0, firstSpace).trim().toLowerCase(); let value = part.substring(firstSpace + 1).trim(); result[key] = value; } } return result; } function escapeHtml(text) { if (text === null || text === undefined) return ""; return String(text) .replace(/&/g, "&amp;") .replace(/</g, "&lt;") .replace(/>/g, "&gt;") .replace(/"/g, "&quot;") .replace(/'/g, "&#039;"); } function infoBox(content) { return "<div style='border:1px solid #999;background:#fff;padding:6px;'>" + content + "</div>"; } function errorBox(content) { return "<div style='border:1px solid #c00;background:#fff;padding:6px;color:#c00;'>" + content + "</div>"; } function whisperGM(content) { return "/w gm " + content; } return { version: VERSION }; }());